﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace ibuddylib {
    /// <summary>
    /// A thread that can execute scheduled events
    /// </summary>
    internal class Timeline : IDisposable {
        private Thread eventThread;
        private volatile bool active;
        private List<TimelineEvent> events = new List<TimelineEvent>();
        private EventWaitHandle wakeup = new EventWaitHandle(false, EventResetMode.AutoReset);

        internal Timeline() {
            active = true;
            eventThread = new Thread(executeEvents);
            eventThread.IsBackground = true;
            eventThread.Start();
        }

        ~Timeline() {
            Dispose();
        }

        private DateTime NextEvent {
            get {
                lock(events) 
                    return events[0].When;
            }
        }

        private void executeEvents() {
            while (active) {
                // Process all events that are due
                while (events.Count > 0 && NextEvent <= DateTime.Now) {
                    lock(events) {
                        events[0].Do();
                        events.RemoveAt(0);
                    }
                }

                // Wait for the next event, or until one is scheduled
                if (events.Count > 0) {
                    var nextEventTime = NextEvent - DateTime.Now;
                    wakeup.WaitOne(nextEventTime.TotalMilliseconds > 0 ? (int)nextEventTime.TotalMilliseconds : 0);
                }
                else {
                    wakeup.WaitOne();
                }
            }
        }
        
        /// <summary>
        /// Schedule an event for a given moment on the timeline
        /// </summary>
        /// <param name="when">When the execute the event</param>
        /// <param name="what"></param>
        internal void ScheduleAt(DateTime when, MethodInvoker what) {
            lock (events) {
                int position = 0;
                while (position < events.Count && when > events[0].When)
                    position++;
                events.Insert(position, new TimelineEvent(when, what));
            }
            wakeup.Set();
        }

        internal void ScheduleIn(TimeSpan interval, MethodInvoker what) {
            ScheduleAt(DateTime.Now + interval, what);
        }

        internal void ScheduleIn(int milliseconds, MethodInvoker what) {
            ScheduleAt(DateTime.Now.AddMilliseconds(milliseconds), what);
        }

        internal void Stop() {
            active = false;
            wakeup.Set();
        }

        public void Dispose() {
            Stop();
        }
    }
}
